package org.infodavid.common.model;

import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Random;
import java.util.Stack;

import org.hsqldb.util.DatabaseManager;
import org.infodavid.common.persistence.IPersistenceProvider;
import org.infodavid.common.persistence.IPersistentData;
import org.infodavid.common.persistence.ITransaction;
import org.infodavid.common.persistence.PersistenceProviderImpl;
import org.infodavid.common.persistence.PersistenceSession;
import org.infodavid.common.persistence.PersistenceSessionWrapper;
import org.joda.time.DateTime;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;

/**
 * The Class AbstractPersistenceTest.
 */
@SuppressWarnings("rawtypes")
public abstract class AbstractPersistenceTest {

  /** The dm. */
  private static DatabaseManager dm = null;

  /** The Constant ITEMS_TO_INSERT. */
  protected static final int ITEMS_TO_INSERT = 5;

  /** The manager. */
  private static final ThreadLocal<IPersistenceProvider> managerTL =
      new ThreadLocal<IPersistenceProvider>() {
        @Override
        protected IPersistenceProvider initialValue() {
          try {
            return new PersistenceProviderImpl();
          }
          catch (Exception e) {
            throw new Error(e);
          }
        }
      };

  /** The random utility. */
  protected static final Random RAND = new Random();

  /** The session. */
  private static final ThreadLocal<PersistenceSession> sessionTL =
      new ThreadLocal<PersistenceSession>() {
        @Override
        protected PersistenceSession initialValue() {
          return managerTL.get().open();
        }

        @Override
        public void remove() {
          try {
            get().close();
          }
          catch (Exception e) {
            System.err.println(e.getMessage());
          }

          super.remove();
        }
      };

  /**
   * Tear down suite.
   */
  @AfterClass
  public static void tearDownSuite() {
    sessionTL.remove();
    managerTL.remove();
  }

  /** The main test (can be null). */
  private final AbstractPersistenceTest parent;

  /** The start date. */
  private DateTime startDate = new DateTime();

  /**
   * Instantiates a new abstract bean test.
   */
  public AbstractPersistenceTest() {
    super();

    parent = null;
  }

  /**
   * Instantiates a new abstract bean test.
   * @param parentTest the parent test
   */
  public AbstractPersistenceTest(final AbstractPersistenceTest parentTest) {
    super();

    parent = parentTest.getParent() == null ? parentTest : parentTest.getParent();
  }

  /**
   * Gets the manager.
   * @return the manager
   */
  public final IPersistenceProvider getManager() {
    return parent == null ? managerTL.get() : parent.getManager();
  }

  /**
   * Gets the main test.
   * @return the main test
   */
  public AbstractPersistenceTest getParent() {
    return parent == null || parent.getParent() == null ? parent : parent.getParent();
  }

  /**
   * Gets the getSession().
   * @return the session
   */
  public final PersistenceSession getSession() {
    return parent == null ? sessionTL.get() : parent.getSession();
  }

  /**
   * Gets the start date.
   * @return the start date
   */
  public DateTime getStartDate() {
    return startDate;
  }

  /**
   * Run database manager.
   */
  protected void runDatabaseManager() {
    final String url = "jdbc:hsqldb:mem:MyDataBase";

    try {
      Class.forName("org.hsqldb.jdbcDriver");

      final Connection con = DriverManager.getConnection(url, "sa", "");

      if (con != null) {
        // needed to patch DatabaseManager so it could
        // be initialized and use the supplied connection
        if (dm == null) {
          dm = new DatabaseManager();
          dm.main();
        }

        con.setAutoCommit(false);
        con.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
        dm.connect(con);
      }
    }
    catch (final Exception e) {
      System.out.println("Can't open the database manager: " + e.getMessage());
    }
  }

  /**
   * Sets the up.
   * @throws Exception the exception
   */
  @Before
  public void setUp() throws Exception {
    startDate = new DateTime();

    try {
      if (parent == null) {
        PersistenceSession session = sessionTL.get();
        ITransaction transaction;

        if (session == null) {
          sessionTL.remove();

          session = sessionTL.get();
          transaction = session.getTransaction();
        }
        else if (session.isOpen()) {
          transaction = session.getTransaction();
        }
        else {
          sessionTL.remove();

          session = sessionTL.get();
          transaction = session.getTransaction();
        }

        if (dm == null) {
          runDatabaseManager();
        }

        transaction.begin();
      }
      else {
        parent.setUp();
      }
    }
    catch (final Exception e) {
      e.printStackTrace();

      throw e;
    }
  }

  /**
   * Tear down.
   * @throws Exception the exception
   */
  @After
  public void tearDown() throws Exception {
    if (parent == null) {
      PersistenceSession session = sessionTL.get();

      if (session instanceof PersistenceSessionWrapper) {
        PersistenceSessionWrapper wrapper = (PersistenceSessionWrapper)session;
        Stack<IPersistentData> inserted = wrapper.getInsertedData();
        IPersistentData data;

        System.out.println("Inserted items: " + inserted.size());

        if (!session.isOpen()) {
          session = getManager().open();
        }

        while (!inserted.isEmpty()) {
          data = inserted.pop();

          try {
            session.removeEntity(data);
          }
          catch (Exception e) {
            System.err
                .println(e.getMessage() + ": " + data.getClass() + " (" + data.getKey() + ')');
          }
        }
      }
    }
    else {
      parent.tearDown();
    }
  }
}
